﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Linq;

using AnB.Common;

namespace AnB.Net
{
    public class Client : NetObject
    {
        public Client(Type connectionClass, params object[] connectionArgs) : base("*client*")
        {
            mConnectionClass = connectionClass;
            mConnectionArgs = connectionArgs;
        }

        private Type mConnectionClass;
        public Type ConnectionClass
        {
            get
            {
                return mConnectionClass;
            }
        }

        private object[] mConnectionArgs;

        private ConnectionBase mConnection;
        public ConnectionBase Connection
        {
            get
            {
                return mConnection;
            }
        }

        public override bool IsStop
        {
            get
            {
                return Connection != null && Connection.IsStop;
            }
        }

        private class ConnectState
        {
            public Action<Error.Code, ConnectionBase> Connected;
            public Socket Socket;
        }

        public void Start(IPEndPoint remote, Action<Error.Code, ConnectionBase> connected)
        {
            Alias = remote.ToString();
            Start((socket, connectState) =>
                {
                    socket.BeginConnect(remote, Connected, connectState);
                }, connected);
        }
        public void Start(IPAddress[] host, int port, Action<Error.Code, ConnectionBase> connected)
        {
            Alias = string.Format("{0}:{1}", string.Join("|", host.AsEnumerable()), port);
            Start((socket, connectState) =>
                {
                    socket.BeginConnect(host, port, Connected, connectState);
                }, connected);
        }

        private void Start(Action<Socket, ConnectState> start, Action<Error.Code, ConnectionBase> connected)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            var connectState = new ConnectState()
            {
                Connected = connected,
                Socket = socket,
            };
            try
            {
                Log.Get().InfoFormat("{0} connecting", Alias);
                start(socket, connectState);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} BeginConnect error {1}\n{2}", Alias, e.Message, e.StackTrace);
                socket.Close();
                Stop();
                connected(Error.Code.ConnectFailed, null);
            }
        }
        public void Start(string host, int port, Action<Error.Code, ConnectionBase> connected)
        {
            Start(Dns.GetHostAddresses(host).ToArray(), port, connected);
        }
        public override void Stop()
        {
            base.Stop();
            var connection = Connection;
            if (connection != null)
            {
                connection.Stop();
            }
        }

        private void Connected(IAsyncResult result)
        {
            var connectState = (ConnectState)result.AsyncState;
            var connected = connectState.Connected;
            var socket = connectState.Socket;
            try
            {
                socket.EndConnect(result);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} EndConnect error {1}\n{2}", Alias, e.Message, e.StackTrace);
                socket.Close();
                connected(Error.Code.ConnectFailed, null);
                return;
            }
            Log.Get().InfoFormat("{0} connected", Alias);

            var connection = mConnection = CreateConnection(socket);
            connection.OnStop += (c) =>
                {
                };
            connection.Start();
            connected(Error.Code.None, connection);
        }
        protected virtual ConnectionBase CreateConnection(Socket socket)
        {
            return (ConnectionBase)Activator.CreateInstance(mConnectionClass, Utils.Flat(socket, mConnectionArgs));
        }
    }
}
